// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 这个文件实现了Check函数，它驱动类型检查。

package types

import (
	"errors"
	"fmt"
	"go/ast"
	"go/constant"
	"go/token"
)

// 调试/开发支持
const (
	debug = false // 在开发期间离开
	trace = false // 启用以获取详细的类型解析跟踪
)

// 如果设置了forceStrict，则类型检查器将强制执行其他
// Go 1规范未指定的规则，但将
// 如果相应的
// 代码被执行。换句话说，程序传入
// 严格模式符合Go 1，但并非所有Go 1程序
// 将以严格模式通过。附加规则包括：
// None
// -类型断言x.（T），其中T是接口类型
// 如果存在任何（静态已知）方法，则无效
// 因为x和T都有不同的签名。
// None
const forceStrict = false

// exprInfo存储有关非类型化表达式的信息。
type exprInfo struct {
	isLhs bool // 表达式是具有延迟类型检查的移位的lhs操作数
	mode  operandMode
	typ   *Basic
	val   constant.Value // 定值；或零（如果不是常数）
}

// 上下文表示在其中检查对象类型的上下文。
type context struct {
	decl          *declInfo              // 检查其初始表达式/函数体的包级声明
	scope         *Scope                 // 查找的最大范围
	pos           token.Pos              // 如果有效，则在位置pos处查找标识符（由Eval使用）
	iota          constant.Value         // 恒定声明中的物联网值；否则为零
	errpos        positioner             // 如果已设置，则为具有继承初始值设定项的常量的标识符位置
	sig           *Signature             // 函数签名（如果在函数内部）；否则为零
	isPanic       map[*ast.CallExpr]bool // 紧急呼叫表达式集（用于终止检查）
	hasLabel      bool                   // 如果函数使用标签，则设置（仅占函数的1%）；未使用的外部函数
	hasCallOrRecv bool                   // 设置表达式是否包含函数调用或通道接收操作
}

// 查找在当前上下文中查找名称并返回匹配的对象，或nil。
func (ctxt *context) lookup(name string) Object {
	_, obj := ctxt.scope.LookupParent(name, ctxt.pos)
	return obj
}

// importKey通过导入路径和源目录标识导入的包
// （包含包含导入的文件的目录）。实际上，目录
// 可能永远都是一样的，也可能不重要。给定一个（导入路径、目录），一个
// 导入程序必须始终返回相同的包（但给定两个不同的导入路径，
// 进口商仍然可以通过将相同的包映射到相同的包来返回相同的包
// 路径）。
type importKey struct {
	path, dir string
}

// dotImportKey描述给定范围内的点导入对象。
type dotImportKey struct {
	scope *Scope
	obj   Object
}

// 检查器维护类型检查器的状态。
// 它必须使用NewChecker创建。
type Checker struct {
	// 包裹信息
	// （由NewChecker初始化，在checker的生命周期内有效）
	conf *Config
	fset *token.FileSet
	pkg  *Package
	*Info
	version version                    // 认可语文版本
	objMap  map[Object]*declInfo       // 将包级对象和（非接口）方法映射到声明信息
	impMap  map[importKey]*Package     // 将（导入路径、源目录）映射到（完整或伪造）包
	posMap  map[*Interface][]token.Pos // 将接口类型映射到嵌入接口位置的列表
	typMap  map[string]*Named          // 将实例化的命名类型哈希映射到*命名类型

	// pkgPathMap将包名称映射到我们已经定义的一组不同的导入路径
	// 在导入图中的任何位置都可以看到该名称。它是用来
	// 消除错误消息中包名称的歧义。
	// None
	// pkgPathMap是惰性分配的，因此我们不需要支付构建的费用
	// 它走上了幸福的道路。seenPkgMap跟踪我们已经完成的包
	// 走。
	pkgPathMap map[string]map[string]bool
	seenPkgMap map[*Package]bool

	// 在一组包文件的类型检查期间收集的信息
	// （由文件初始化，仅在check.Files期间有效；
	// 地图和列表按需分配）
	files        []*ast.File               // 包文件
	imports      []*PkgName                // 进口包裹清单
	dotImportMap map[dotImportKey]*PkgName // 将点导入的对象映射到它们通过点导入的包

	firstErr error                 // 遇到的第一个错误
	methods  map[*TypeName][]*Func // 将包作用域类型名称映射到关联的非空（非接口）方法
	untyped  map[ast.Expr]exprInfo // 没有最终类型的表达式映射
	delayed  []func()              // 延迟动作片段的堆栈；段按FIFO顺序处理
	objPath  []Object              // 类型推断期间对象依赖项的路径（用于周期报告）

	// 检查当前对象类型的上下文
	// （仅在特定对象的类型检查期间有效）
	context

	// 调试
	indent int // 用于跟踪的缩进
}

// 如果存在check.decl，addDeclDep会添加依赖项边缘（check.decl->to）
func (check *Checker) addDeclDep(to Object) {
	from := check.decl
	if from == nil {
		return // 不在包级别的初始化表达式中
	}
	if _, found := check.objMap[to]; !found {
		return // to不是包级别的对象
	}
	from.addDep(to)
}

func (check *Checker) rememberUntyped(e ast.Expr, lhs bool, mode operandMode, typ *Basic, val constant.Value) {
	m := check.untyped
	if m == nil {
		m = make(map[ast.Expr]exprInfo)
		check.untyped = m
	}
	m[e] = exprInfo{lhs, mode, typ, val}
}

// later将f推到稍后处理的操作堆栈上；
// 在当前语句的末尾，或者在局部常量的情况下
// 或变量声明，在常量或变量位于范围之前
// （因此f在任何新声明之前仍然可以看到范围）。
func (check *Checker) later(f func()) {
	check.delayed = append(check.delayed, f)
}

// push将obj推到对象路径上，并返回其在路径中的索引。
func (check *Checker) push(obj Object) int {
	check.objPath = append(check.objPath, obj)
	return len(check.objPath) - 1
}

// 弹出并返回对象路径中最顶端的对象。
func (check *Checker) pop() Object {
	i := len(check.objPath) - 1
	obj := check.objPath[i]
	check.objPath[i] = nil
	check.objPath = check.objPath[:i]
	return obj
}

// NewChecker返回给定包的新Checker实例。
// 可以通过checker.files以增量方式添加包文件。
func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Checker {
	// 确保我们有一个配置
	if conf == nil {
		conf = new(Config)
	}

	// 确保我们有一个信息结构
	if info == nil {
		info = new(Info)
	}

	version, err := parseGoVersion(conf.goVersion)
	if err != nil {
		panic(fmt.Sprintf("invalid Go version %q (%v)", conf.goVersion, err))
	}

	return &Checker{
		conf:    conf,
		fset:    fset,
		pkg:     pkg,
		Info:    info,
		version: version,
		objMap:  make(map[Object]*declInfo),
		impMap:  make(map[importKey]*Package),
		posMap:  make(map[*Interface][]token.Pos),
		typMap:  make(map[string]*Named),
	}
}

// initFiles初始化检查器的文件特定部分。
// 提供的文件必须全部属于同一个包。
func (check *Checker) initFiles(files []*ast.File) {
	// 从头开始（check.Files可能会被多次调用）
	check.files = nil
	check.imports = nil
	check.dotImportMap = nil

	check.firstErr = nil
	check.methods = nil
	check.untyped = nil
	check.delayed = nil

	// 确定包名并收集有效文件
	pkg := check.pkg
	for _, file := range files {
		switch name := file.Name.Name; pkg.name {
		case "":
			if name != "_" {
				pkg.name = name
			} else {
				check.errorf(file.Name, _BlankPkgName, "invalid package name _")
			}
			fallthrough

		case name:
			check.files = append(check.files, file)

		default:
			check.errorf(atPos(file.Package), _MismatchedPkgName, "package %s; expected %s", name, pkg.name)
			// 忽略此文件
		}
	}
}

// 紧急救助用于提前终止合同。
type bailout struct{}

func (check *Checker) handleBailout(err *error) {
	switch p := recover().(type) {
	case nil, bailout:
		// 正常返回或提前退出
		*err = check.firstErr
	default:
		// 再次恐慌
		panic(p)
	}
}

// 文件作为检查器包的一部分检查提供的文件。
func (check *Checker) Files(files []*ast.File) error { return check.checkFiles(files) }

var errBadCgo = errors.New("cannot use FakeImportC and go115UsesCgo together")

func (check *Checker) checkFiles(files []*ast.File) (err error) {
	if check.conf.FakeImportC && check.conf.go115UsesCgo {
		return errBadCgo
	}

	defer check.handleBailout(&err)

	check.initFiles(files)

	check.collectObjects()

	check.packageObjects()

	check.processDelayed(0) // 包括所有功能

	check.initOrder()

	if !check.conf.DisableUnusedImportCheck {
		check.unusedImports()
	}

	check.recordUntyped()

	if check.Info != nil {
		sanitizeInfo(check.Info)
	}

	check.pkg.complete = true

	// 不再需要-释放内存
	check.imports = nil
	check.dotImportMap = nil
	check.pkgPathMap = nil
	check.seenPkgMap = nil

	// TODO（rFindley）此时我们应该释放更多内存。

	return
}

// processDelayed处理在top之后推送的所有延迟操作。
func (check *Checker) processDelayed(top int) {
	// 如果每个延迟的操作都推动一个新的操作，则
	// 堆栈将在此循环期间继续增长。
	// 但是，只有处理函数（
	// 以延迟方式处理）可能
	// 添加更多操作（例如嵌套函数），以便
	// 这是一个充分有界的过程。
	for i := top; i < len(check.delayed); i++ {
		check.delayed[i]() // 可以附加到check.delayed
	}
	assert(top <= len(check.delayed)) // 堆栈不能收缩
	check.delayed = check.delayed[:top]
}

func (check *Checker) record(x *operand) {
	// 将x转换为用户友好的值集
	// TODO（gri）此代码可以简化
	var typ Type
	var val constant.Value
	switch x.mode {
	case invalid:
		typ = Typ[Invalid]
	case novalue:
		typ = (*Tuple)(nil)
	case constant_:
		typ = x.typ
		val = x.val
	default:
		typ = x.typ
	}
	assert(x.expr != nil && typ != nil)

	if isUntyped(typ) {
		// 延迟类型和值记录，直到我们知道类型
		// 或者直到类型检查结束
		check.rememberUntyped(x.expr, false, x.mode, typ.(*Basic), val)
	} else {
		check.recordTypeAndValue(x.expr, x.mode, typ, val)
	}
}

func (check *Checker) recordUntyped() {
	if !debug && check.Types == nil {
		return // 无事可做
	}

	for x, info := range check.untyped {
		if debug && isTyped(info.typ) {
			check.dump("%v: %s (type %s) is typed", x.Pos(), x, info.typ)
			unreachable()
		}
		check.recordTypeAndValue(x, info.mode, info.typ, info.val)
	}
}

func (check *Checker) recordTypeAndValue(x ast.Expr, mode operandMode, typ Type, val constant.Value) {
	assert(x != nil)
	assert(typ != nil)
	if mode == invalid {
		return // 省略
	}
	if mode == constant_ {
		assert(val != nil)
		// 我们在这里检查is（typ，IsConstType）作为常量表达式
		// 记录为类型参数。
		assert(typ == Typ[Invalid] || is(typ, IsConstType))
	}
	if m := check.Types; m != nil {
		m[x] = TypeAndValue{mode, typ, val}
	}
}

func (check *Checker) recordBuiltinType(f ast.Expr, sig *Signature) {
	// f必须是a（可能带括号，可能带限定符）
	// 表示内置（包括不安全的非常量）的标识符
	// 函数Add和Slice）：记录f和可能的
	// 儿童
	for {
		check.recordTypeAndValue(f, builtin, sig, nil)
		switch p := f.(type) {
		case *ast.Ident, *ast.SelectorExpr:
			return // 我们结束了
		case *ast.ParenExpr:
			f = p.X
		default:
			unreachable()
		}
	}
}

func (check *Checker) recordCommaOkTypes(x ast.Expr, a [2]Type) {
	assert(x != nil)
	if a[0] == nil || a[1] == nil {
		return
	}
	assert(isTyped(a[0]) && isTyped(a[1]) && (isBoolean(a[1]) || a[1] == universeError))
	if m := check.Types; m != nil {
		for {
			tv := m[x]
			assert(tv.Type != nil) // 应该已经有记录了
			pos := x.Pos()
			tv.Type = NewTuple(
				NewVar(pos, check.pkg, "", a[0]),
				NewVar(pos, check.pkg, "", a[1]),
			)
			m[x] = tv
			// 如果x是括号表达式（p.x），则更新p.x
			p, _ := x.(*ast.ParenExpr)
			if p == nil {
				break
			}
			x = p.X
		}
	}
}

func (check *Checker) recordInferred(call ast.Expr, targs []Type, sig *Signature) {
	assert(call != nil)
	assert(sig != nil)
	if m := getInferred(check.Info); m != nil {
		m[call] = _Inferred{targs, sig}
	}
}

func (check *Checker) recordDef(id *ast.Ident, obj Object) {
	assert(id != nil)
	if m := check.Defs; m != nil {
		m[id] = obj
	}
}

func (check *Checker) recordUse(id *ast.Ident, obj Object) {
	assert(id != nil)
	assert(obj != nil)
	if m := check.Uses; m != nil {
		m[id] = obj
	}
}

func (check *Checker) recordImplicit(node ast.Node, obj Object) {
	assert(node != nil)
	assert(obj != nil)
	if m := check.Implicits; m != nil {
		m[node] = obj
	}
}

func (check *Checker) recordSelection(x *ast.SelectorExpr, kind SelectionKind, recv Type, obj Object, index []int, indirect bool) {
	assert(obj != nil && (recv == nil || len(index) > 0))
	check.recordUse(x.Sel, obj)
	if m := check.Selections; m != nil {
		m[x] = &Selection{kind, recv, obj, index, indirect}
	}
}

func (check *Checker) recordScope(node ast.Node, scope *Scope) {
	assert(node != nil)
	assert(scope != nil)
	if m := check.Scopes; m != nil {
		m[node] = scope
	}
}
